Verilog 上で定義したメモリへのファームウェアの書き込み
$readmemh 関数を使って Verilog 上で定義したメモリにファームウェアを書き込むことができる。
$readmemh 関数で読み込むことができるフォーマットは、16進数の並びをテキストで記述した HEX ファイルフォーマットなので、objcopy コマンドを使って実行ファイル形式である ELF から HEX ファイルに変換する必要がある。
ファームウェア書き込みの流れ
code:text
アセンブリファイル (foo.S)
↓
(アセンブラにかける)
↓
オブジェクトファイル(foo.o)
↓
(リンカにかける)
↓
実行ファイル(foo.elf)
↓
(objcopyにかける)
↓
HEXファイル
↓
$readmemh 関数を使って Verilog 上のメモリへロードする
HEXファイルの作り方
以下のようなアセンブリファイルを用意し、
code:firmware/blink.S
// LED に 1010_1010 のパターンを出力
.text
.globl _start
_start:
lui x10, 0xf0001 // LEDアクセス用 gp
addi x5, x0, 0xAA // x5 = 1010_1010
sw x5, 0(x10) // LED に x5 を出力
loop:
jal x0, loop
アセンブルしてできた ELF ファイルを、objcopy で Verilog で読み込み可能な HEX ファイルへ変換する。
code:sh
$ cd firmware/
# blink.S をアセンブルして、オブジェクトファイル blink.o を作成
$ riscv64-unknown-elf-gcc -c -march=rv32i -mabi=ilp32 blink.S -o blink.o
# blink.o をリンカにかけて、実行ファイル(ELFファイル)を生成
# プログラム(TEXTセグメント)のアドレスは 0x00000000 から始まる
$ riscv64-unknown-elf-gcc -nostdlib -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00000000 blink.o -o firmware.elf
# 実行ファイルから HEX ファイルを生成
$ riscv64-unknown-elf-objcopy -O verilog --verilog-data-width 4 firmware.elf firmware.hex
-march=rv32i と -mabi=ilp32
作成中のCPUはRISC-Vのコンパクト命令をサポートしてないので、アセンブリのパラメータに -march=rv32i -mabi=ilp32 をつけることでコンパクト命令を出力しないようにする。
code:sh
$ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -Wl,-Ttext=0x00000000 $(TARGET).S -o firmware.elf
HEXファイルの読み込み
作成した HEX ファイルは $readmemh でメモリへ読み込む。
code:verilog
// BRAM コントローラー
module bram_controller(
...
);
// 0x0000 ~ 0x1FFF の 8KB の BRAM を用意
// 1ワード = 32bit = 4byte なので、メモリの深さは 2048 となる
initial begin
// HEX ファイルを読み込む
$readmemh("firmware/firmware.hex", mem);
end
有効なHEXファイルと無効なHEXファイル
今回は、Verilog 上で以下の書き方でメモリを定義した。
code:verilog
このメモリへHEXファイルを読み込む際、OKなHEXファイルとNGなHEXファイルがあった。何が原因でNGだったのかを確認する。
OK
code:firmware.mem
F0001537
0AA00293
00552023
0000006F
NG。上記のメモリの定義では1ワードが4バイト。ワードの間に区切りを入れてはダメみたい。
code:firmware.mem
F0 00 15 37
0A A0 02 93
00 55 20 23
00 00 00 6F
OK。ワードの区切りは、改行でもスペースでもどちらでも大丈夫みたい。
code:firmware.mem
F0001537 0AA00293 00552023 0000006F
OK。objcopy コマンドが自動でつける @00000000 はついていても大丈夫みたい。
code:firmware.mem
@00000000
F0001537
0AA00293
00552023
0000006F
これもOK。@ を使うことで指定したアドレスへデータを配置してくれるみたい。
code:firmware.mem
@00000000
F0001537
@00000004
0AA00293
@00000008
00552023
@0000000C
0000006F
ecpbram を使ったファームウェアの差し替え
ファームウェアのソースコードを書き換える度に Verilog のビルドを行うのは時間がかかり過ぎるので、Verilog の出力ファイル中のファームウェアだけを置き換えたい。
ecpbram コマンドを使うことでそれが実現できる。ecpbram -g でダミー用の seed データを作成して Verilog へ埋め込み、後から ecpbram -i -o -f -t を使って seed データの置き換えを行う。
code:diff
diff --git a/.gitignore b/.gitignore
index 0f2c82e..2971ffd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@ a.out
odeeen.json
ulx3s.bit
ulx3s_out.config
+ulx3s_out_final.config
firmware/*.o
firmware/firmware.elf
firmware/firmware.hex
+firmware/firmware_seed.hex
\ No newline at end of file
diff --git a/Makefile b/Makefile
index f41f858..bfb1a79 100644
--- a/Makefile
+++ b/Makefile
@@ -3,15 +3,23 @@
all: ulx3s.bit
clean:
- rm -rf odeeen.json ulx3s_out.config ulx3s.bit firmware/firmware.hex firmware/firmware.elf
+ rm -rf odeeen.json ulx3s_out.config ulx3s_out_final.config ulx3s.bit firmware/firmware.hex firmware/firmware.elf
+
+
+ulx3s.bit: ulx3s_out.config firmware/firmware.hex
+ # NOTE: BRAM上のダミーデータで埋めてる箇所を、実際のファームウェアに置き換える
+ ecpbram -i ulx3s_out.config \
+ -o ulx3s_out_final.config \
+ -f firmware/firmware_seed.hex \
+ -t firmware/firmware.hex
+
+ ecppack ulx3s_out_final.config ulx3s.bit
-ulx3s.bit: ulx3s_out.config
- ecppack ulx3s_out.config ulx3s.bit
ulx3s_out.config: odeeen.json
nextpnr-ecp5 --85k --json odeeen.json --lpf rtl/ulx3s_v20.lpf --textcfg ulx3s_out.config
-odeeen.json: rtl/cpu.sv rtl/ulx3s_top.sv rtl/bram_controller.sv rtl/uart.v firmware/firmware.hex
+odeeen.json: rtl/cpu.sv rtl/ulx3s_top.sv rtl/bram_controller.sv rtl/uart.v firmware/firmware_seed.hex
yosys -p "hierarchy -top ulx3s_top" -p "synth_ecp5 -json odeeen.json" rtl/cpu.sv rtl/bram_controller.sv rtl/ulx3s_top.sv rtl/uart.v
prog: ulx3s.bit
@@ -30,3 +38,7 @@ firmware/firmware.hex: firmware/$(FIRMWARE_TARGET)
riscv64-unknown-elf-objcopy -O verilog --verilog-data-width 4 firmware/firmware.elf firmware/firmware.hex
firmware: firmware/firmware.hex
+
+# ファームウェア置き換え用のダミーデータを生成
+firmware/firmware_seed.hex:
+ ecpbram -g firmware/firmware_seed.hex -w 32 -d 2048
diff --git a/rtl/bram_controller.sv b/rtl/bram_controller.sv
index 94b0058..74b4a6d 100644
--- a/rtl/bram_controller.sv
+++ b/rtl/bram_controller.sv
@@ -36,7 +36,8 @@ module bram_controller(
assign mem_ready = (state_reg == STATE_SEND_READY) ? 1'b1 : 1'b0;
initial begin
- $readmemh("firmware/firmware.hex", mem);
+ // NOTE: ビルド時はダミーデータを書き込んでおき、後からダミーデータとファームウェアの置き換えを行う
+ $readmemh("firmware/firmware_seed.hex", mem);
end
always_ff @(posedge clk) begin
参考サイト
Initialize Memory in Verilog